// 文件/数据库交互模块，直接跟文件与数据库进行数据交互的模块，对文件和数据库进行增删改查，其下有四个子模块：
// 1.（查）题目信息提取模块：提取文件或数据库中的题目信息，并将其返回给调用者。
// 以下为后续计划增加的管理员录题功能：
// 2.（增）增加题目模块：往文件或数据库中增加题目信息。
// 3.（删）删除题目模块：删除文件或数据库中的题目信息。
// 4.（改）修改题目模块：修改文件或数据库中的题目信息。
// MySQL版本

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <cstdlib> //atoi()
#include <cassert>

#include "../common/util.hpp" //引入common目录下的工具模块，StringUtil
#include "../common/log.hpp"  //引入common目录下的工日志模块
#include <mysql/mysql.h>

namespace ns_model4
{
    using namespace std;
    using namespace ns_util; // 展开工具模块
    using namespace ns_log;  // 展开日志模块

    // Question结构体：用来描述一个题目，里面保存着一个题目必要的信息
    struct Question4
    {
        std::string number; // 题目编号，唯一
        std::string title;  // 题目的标题
        std::string star;   // 难度: 简单 中等 困难
        int cpu_limit;      // 题目的时间要求(S)
        int mem_limit;      // 题目的空间要去(KB)
        std::string desc;   // 题目的描述
        std::string header; // 题目预设给用户在线编辑器的代码
        std::string input;  // 输入(用例)
        std::string answer; // 每个用例的答案
    };

    const std::string oj_questions = "oj_questions4"; // 数据库oj中存放题目的表名
    const std::string host = "127.0.0.1";             // mysql服务端的ip地址(本地回环)
    const std::string user = "root";                  // 访问mysql服务端的用户
    const std::string passwd = "123456";              // 访问mysql服务端的用户的密码
    const std::string db = "oj";                      // 要访问的数据库
    const int port = 3306;                            // mysql服务端的端口号

    class MysqlUtil
    {
    public:
        // 获取一个数据库连接
        static void GetMysqlConnect(MYSQL **mysql)
        {
            MYSQL *my = mysql_init(nullptr);

            if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0))
            {
                LOG(FATAL) << "连接数据库失败!\n";
                *mysql = nullptr;
                return;
            }
            LOG(INFO) << "连接数据库成功!\n";

            mysql_set_character_set(my, "utf8");
            *mysql = my;
        }
    };

    class Model4
    {
    public:
        Model4()
        {
        }
        ~Model4()
        {
        }

        /*查询数据库函数*/
        bool QueryMySql(const std::string &sql, vector<Question4> *out)
        {
            // 1. 创建并初始化mysql对象（也叫创建mysql句柄）
            MYSQL *my = mysql_init(nullptr);

            // 3. 链接数据库(使用指定用户登录指定数据库，该用户必须有远程访问权限)
            // 初始化完毕之后，必须先链接数据库，在进行后续操作。（mysql网络部分是基于TCP/IP的）
            if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0))
            {
                LOG(FATAL) << "连接数据库失败!OJ系统无法继续运行！\n";
                return false;
            }
            LOG(INFO) << "连接数据库成功!\n";

            // 4. 设置链接的默认字符集为utf8，原始默认是latin1(拉丁文)
            mysql_set_character_set(my, "utf8");

            // 5. 使用SQL对上面指定的数据库进行操作
            if (0 != mysql_query(my, sql.c_str())) // 不为0表示sql语句执行失败
            {
                LOG(WARNING) << sql << " 查询数据库的sql语句执行失败，请尽快查看。\n";
                return false;
            }

            // 6. 获取执行sql语句的结果
            // 6.1 从myql对象里读取结果
            MYSQL_RES *res = mysql_store_result(my);
            // 6.2 从MYSQL_RES对象里获取行数和列数
            int rows = mysql_num_rows(res);   // 获得行数量
            int cols = mysql_num_fields(res); // 获得列数量

            // 6.5 从MYSQL_RES对象里一行一行的读取结果生成Question对象，并将对象保存到输出型参数中
            Question4 q;
            for (int i = 0; i < rows; i++)
            {
                MYSQL_ROW row = mysql_fetch_row(res);
                q.number = row[0];
                q.title = row[1];
                q.star = row[2];
                q.desc = row[3];
                q.header = row[4];
                q.input = row[5];
                q.answer = row[6];
                q.cpu_limit = atoi(row[7]);
                q.mem_limit = atoi(row[8]);

                out->push_back(q);
            }

            // free(res); //记得free MYSQL_RES对象，如果是较新版本的开发库会自动释放，删除此句即可。
            // 2. 关闭mysql连接
            mysql_close(my);

            return true;
        }

        /*获取所有题目的信息*/
        bool GetAllQuestions(vector<Question4> *out)
        {
            // 1.拼接sql语句
            std::string sql = "select * from ";
            sql += oj_questions;

            // 2.调用查询数据库函数
            return QueryMySql(sql, out);
        }
        /*获取一道题目的信息*/
        bool GetOneQuestion(const std::string &number, Question4 *q)
        {
            // 1.拼接sql语句
            std::string sql = "select * from ";
            sql += oj_questions;
            sql += " where number=";
            sql += number;

            // 2.调用查询数据库函数
            vector<Question4> result;
            if (QueryMySql(sql, &result))
            {
                if (result.size() == 1)
                {
                    *q = result[0];
                    return true; // 只查到一题才算成功
                }
            }
            return false;
        }

        int AcceptOneQuestion(int userid, Question4 *q)
        {
            // 数据库加加
            // INSERT INTO oj_correct(userid,number, title) SELECT '2',
            // '1','1' FROM DUAL WHERE NOT EXISTS(SELECT *
            // FROM oj_correct WHERE userid = '2' and number='1')
            std::string sql = "insert into oj_correct(userid, number, title) select '" + std::to_string(userid) + "','" + q->number + "','" + q->title + "'" +
                              "from dual where not exists(select * from oj_correct where userid = '" + std::to_string(userid) + "' and number='" + q->number + "')";
            std::cout << sql << std::endl;
            MYSQL *my;
            MysqlUtil::GetMysqlConnect(&my);

            if (0 != mysql_query(my, sql.c_str()))
            {
                mysql_close(my);
                return false;
            }

            // update users set correctnum = (select count(*) from oj_correct where userid='1079') where userid='1079';
            sql = "update users set correctnum = (select count(*) from oj_correct where userid='" + std::to_string(userid) + "')" +
                  " where userid='" + std::to_string(userid) + "'";
            std::cout << sql << std::endl;
            if (0 != mysql_query(my, sql.c_str()))
            {
                mysql_close(my);
                return false;
            }

            mysql_close(my);
            return true;
        }
    };
}